package com.lechi.web.server;


import com.lechi.iot.netty.channel.ITcpChannel;
import com.lechi.iot.netty.channel.TcpChannelGroup;
import com.lechi.iot.netty.device.TcpControlCentre;
import com.lechi.iot.netty.device.TcpDeviceChannel;
import com.lechi.iot.netty.handle.TcpExceptionHandler;
import com.lechi.iot.netty.handle.TcpListenerHandler;
import com.lechi.iot.netty.handle.TcpMessageDecoder;
import com.lechi.iot.netty.handle.TcpMessageEncoder;
import com.lechi.iot.netty.listener.TcpListener;
import com.lechi.web.constants.Constants;
import com.lechi.web.server.connect.TcpServerConnect;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.openhft.affinity.AffinityStrategies;
import net.openhft.affinity.AffinityThreadFactory;

import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * 处理报文的通讯服务端
 */
@Slf4j
public class TcpServer extends ChannelInitializer<SocketChannel> implements TcpControlCentre {

    private static int IDLE_TIMEOUT = 60000;

    private final int port;

    private final int workThread;

    private final String protocolType;

    private final TcpListenerHandler tcpListenerHandler;

    private EventLoopGroup bossGroup;

    private EventLoopGroup workerGroup;

    private ChannelFuture future;

    @Getter
    private TcpChannelGroup groups;

    /**
     * 创建指定服务端口，指定线程数的服务端
     *
     * @param port          服务端口
     * @param workThread    执行线程池线程数
     * @param heartBeatTime 心跳检测超时时间(单位：毫秒)
     * @param protocolType  协议类型
     */
    public TcpServer(int port, int workThread, int heartBeatTime, String protocolType) {
        this.port = port;
        this.workThread = workThread;
        IDLE_TIMEOUT = heartBeatTime;
        this.protocolType = protocolType;
        this.groups = new TcpChannelGroup();
        this.tcpListenerHandler = new TcpListenerHandler(this.groups);
    }

    /**
     * 启动服务
     */
    public void start() {
        ServerBootstrap b = new ServerBootstrap();
        if (Epoll.isAvailable()) {
            bossGroup = new EpollEventLoopGroup(1);
            ThreadFactory threadFactory = new AffinityThreadFactory("worker", AffinityStrategies.DIFFERENT_CORE);
            workerGroup = new EpollEventLoopGroup(workThread, threadFactory);
            b.group(bossGroup, workerGroup)  // 设置 主从 线程组 , 分别对应 主 Reactor 和 从 Reactor
                    .channel(EpollServerSocketChannel.class) //ServerSocketChannel 是一个用于监听客户端连接请求的通道。它是一个可以注册为非阻塞操作的服务器套接字通道，允许做多路复用和异步I/O操作。
                    .handler(new LoggingHandler(LogLevel.INFO)) //绑定到端口并监听客户端连接请求
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //是Netty提供的一个内存分配器，它使用池化内存来提高性能。 Boss线程池内存池配置.
                    .option(ChannelOption.SO_REUSEADDR, true) //option()设置的是服务端用于接收进来的连接，也就是boosGroup线程。
                    .option(EpollChannelOption.SO_REUSEPORT, true)
                    .childOption(ChannelOption.TCP_NODELAY, true) // 禁用了Nagle算法，允许小包的发送  childOption()是父管道接收到的连接，也就是workerGroup线程。
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置连接状态行为, 保持连接状态
                    .childOption(ChannelOption.SO_LINGER, -1)  // 默认值为-1，表示禁用该功能 表示socket.close()方法立即返回，但OS底层会将发送缓冲区的数据全部发送到对端
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //Work线程池内存池配置.
                    .childHandler(this);
            //通过这个SimpleChannelPool管理的所有Channel都将使用这个分配器，从而能够重用缓冲区。
            // 这样做可以提高Netty在处理大量数据时的性能，因为它减少了内存的分配和释放次数，提高了内存的复用效率
        } else {
            bossGroup = new NioEventLoopGroup(1);
            ThreadFactory threadFactory = new AffinityThreadFactory("worker", AffinityStrategies.DIFFERENT_CORE);
            workerGroup = new NioEventLoopGroup(workThread, threadFactory);
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)  // 设置 NIO 网络套接字通道类型，它使用了零拷贝技术来提高性能
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_REUSEADDR, true)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(this);
        }
        future = b.bind(port);
    }

    /**
     * 停止服务
     */
    public void stop() {
        future.channel().closeFuture();
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    /**
     * 根据名称/地址找已连接设备组
     * 名称/地址不存在或者未连接时返回null值
     *
     * @param name 名称/地址
     * @return 设备组 tcp channel
     */
    public ITcpChannel group(String name) {
        return get(name);
    }

    /**
     * 列出所有已连接设备组清单
     *
     * @return 所有已连接身边组清单
     */
    public List<ITcpChannel> groups() {
        return groups.list();
    }

    /**
     * 添加接收指定指令的消息监听器
     *
     * @param command  指令类型
     * @param listener 消息监听器
     */
    public void registerListener(int command, TcpListener listener) {
        tcpListenerHandler.addListener(command, listener);
    }

    /**
     * 移除接收指定指令的消息监听器
     *
     * @param command 指令类型
     * @return 移除消息监听器 ，如果没有返回null
     */
    public TcpListener removeListener(int command) {
        return tcpListenerHandler.removeListener(command);
    }

    @Override
    protected void initChannel(SocketChannel ch) {
        //SocketChannel 代表一个连接到某个网络套接字的通道。它用于在客户端和服务器之间进行双向通信，支持阻塞和非阻塞模式。
//        非阻塞支持：也可以配置为非阻塞，这样多个SocketChannel可以同时工作而不阻塞主线程。
//        Buffer支持：可以通过ByteBuffer类处理数据的读取和写入，提供高效的I/O处理。   与ServerSocketChannel相连接的客户端通道
        ChannelPipeline channelPipeline = ch.pipeline();
        ITcpChannel deviceChannel = new TcpDeviceChannel(ch);
        add(deviceChannel);
        //服务端心跳检测超时时间，超时则主动断开链接
        channelPipeline.addLast(new IdleStateHandler(0, 0, IDLE_TIMEOUT, TimeUnit.MILLISECONDS));
        channelPipeline.addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                if (evt instanceof IdleStateEvent) {
                    log.info("服务端Socket服务指定时间 {} 范围内没有读写操作，断开连接， ：", IDLE_TIMEOUT);
                    ctx.disconnect();
                } else {
                    super.userEventTriggered(ctx, evt);
                }
            }
        });
        if (Constants.MODBUS_RTU.equals(this.protocolType)) {
            channelPipeline.addLast(new TcpMessageEncoder());//自定义编码器，数据编码后下发
            channelPipeline.addLast(new TcpMessageDecoder());
            channelPipeline.addLast(tcpListenerHandler); //推荐的做法是在 ChannelHandler 处理器中自定义新的业务线程池，将耗时的操作提交到业务线程池中执行。
        }
        channelPipeline.addLast(new TcpServerConnect());
        channelPipeline.addLast(new TcpExceptionHandler());
    }

    @Override
    public boolean add(ITcpChannel channel) {
        return groups.add(channel);
    }

    @Override
    public ITcpChannel remove(String name) {
        return groups.remove(name);
    }

    @Override
    public ITcpChannel get(String name) {
        return groups.get(name);
    }

}
